Skip to content

chore: snapshot upstream main with spawn isolation guard - #63

Merged
withally merged 113 commits into
mainfrom
fm/fm-upstream-cutover-m1
Aug 21, 2026
Merged

chore: snapshot upstream main with spawn isolation guard#63
withally merged 113 commits into
mainfrom
fm/fm-upstream-cutover-m1

Conversation

@withally

@withally withally commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Cutover

Operator-visible change

The fork adopts the complete current upstream product and operational surface. Fork-only Grok coordination, watcher fixes, policy, and other behavior are deliberately not retained. The sole restore is the spawn safety check that refuses the primary checkout by device and inode identity, including case-variant path spellings on case-insensitive macOS.

Equivalence pass

Current upstream main already refuses obvious primary-checkout and non-worktree paths, but bin/fm-spawn.sh still compares pwd -P path text in worktree validation, relaunch validation, and the post-treehouse settle loop. It has no filesystem-identity helper or stat-based equivalent. Fork PR #60 routes those comparisons through device and inode identity; this PR ports that guard to the current upstream shape and retains its public-interface regression, live macOS proof, runner registration, and Herdr fixture compatibility.

CI delivery contract

This is a captain-approved direct PR and must not run the no-mistakes pipeline. The PR must be raised via no-mistakes process-stamp check is therefore expected to remain red by design; it is not a test failure and the default branch is unprotected. Every other CI check must pass.

Conservation and verification

  • Snapshot diff against upstream main: empty.
  • Final non-guard diff against upstream main: empty.
  • Final changed path identities exactly match the five path identities changed by fork PR fix(spawn): reject primary checkout by filesystem identity in worktree guard #60: bin/fm-spawn.sh, bin/fm-test-run.sh, tests/fm-backend-herdr-presentation-e2e.test.sh, tests/fm-spawn-worktree-identity-live.test.sh, and tests/fm-tangle-guard.test.sh.
  • Upstream and snapshot each contain 411 tracked tree entries; the final tree contains 412 because the authorized live regression is restored.
  • Passed tests/fm-tangle-guard.test.sh.
  • Passed FM_SPAWN_CASE_IDENTITY_LIVE=1 tests/fm-spawn-worktree-identity-live.test.sh on Darwin with a real case-variant path.
  • Passed tests/fm-spawn-worktree-settle.test.sh.
  • Passed tests/fm-backend-herdr-presentation-e2e.test.sh against Herdr 0.7.5.
  • bin/fm-lint.sh completed pinned ShellCheck 0.11.0; local workflow lint could not run because actionlint 1.7.12 is not installed, so PR CI is the authoritative workflow-lint gate.

kunchenguid and others added 30 commits August 2, 2026 14:42
* perf(ci): shard the portable serial behavior lane across runners

The Behavior portable serial job ran all 69 scripts of the serial
remainder on one runner. The measured serial sum on run 30725985757 was
1143762 ms (19m04s) against a 20-minute timeout, so the job intermittently
reached the cap and was cancelled with every step passing. Setup is only
about 7s, so the cost is entirely test wall time.

Split the lane into four separate-runner shards. Each shard is still
strictly serial, and separate runners mean no two of these stateful
scripts ever share a machine, so the split needs no concurrency isolation
proof. Assignment is longest-processing-time bin packing over measured
per-script duration hints, balancing every shard to 285941 ms (~4m46s) of
expected work, and the timeout tightens from 20 to 15 minutes.

bin/fm-test-run.sh owns the shard count and refuses a lane whose "ofN"
disagrees with it, while ci.yml derives the same count from
strategy.job-total rather than a literal, so changing it in either file
alone fails the lane loudly instead of leaving part of the required suite
unrun. --check-coverage additionally proves the shards are non-empty,
disjoint, and exactly equal to the serial lane. No test is weakened,
skipped, or removed.

Also replace the wall-clock sleeps in the --jobs scheduler test fixture
with an explicit signal handshake between the fixtures. The old
0.5s-versus-0.05s race failed on a loaded machine; the handshake passes
under sustained CPU saturation.

* no-mistakes(review): Correct portable serial shard balance evidence

* no-mistakes(document): Document portable serial shard evidence accurately
…henguid#1545)

* fix(bin): identify harness sessions by path and report delivered wakes

Two supervision faults, both reported by a contributor and both open on the
default branch.

Fault 1: the Stop auto-arm never claims the home. fm_harness_ancestry_pid()
matched only the basename of `ps -o comm=`, and Claude Code's native installer
names the per-session executable by its version (.../share/claude/versions/
2.1.220), so that basename identifies nothing. Three real failure shapes follow:
a version-named session is missed entirely and the hook exits 0 with the epoch
never written (unconditional on Linux, where procps reports the kernel exec name
and ignores argv[0]); a claude-named daemon that directly parents sessions wins
the outermost-contiguous-claude rule ahead of the session itself; and a session
that is both version-named and daemon-parented has its live lock reclaimed as
stale and rewritten to the shared daemon pid, corrupting the home's ownership
record.

Harness identity now also reads whole components of the executable path and of
argv[0], which is what both platforms still carry. Matching whole components
only keeps that widening safe: bin/fm-claude-stop-autoarm.sh and ~/.claude/hooks
scripts have no "claude" component. Ownership is then decided against the
session's whole contiguous harness ancestry rather than one chosen pid, which is
the honest form of the question the library already documents ("does the current
process descend from that same harness?"). That subsumes the outermost-pid rule
for Claude's nested bg-spare worker chain instead of reverting it, and lets a
daemon-parented session recognize its own lock. Lock acquisition still writes the
outermost pid of the run, the only pid that lives as long as the session.

Fault 2: an attached arm reports a delivered cycle as FAILED. The watcher prints
its one reason line to its own stdout, so only the arm that forked it can read
that line; an arm that attached observes nothing but a released lock and called a
completely successful cycle "cycle ended without an actionable reason". No
supervision event was lost - the durable queue held it - but every harness
protocol reads that line as "supervision is down" and directs a manual re-arm.

The arm now resolves an unobservable close against the durable wake queue, which
records every wake before the watcher prints it and whose sequence counter never
rewinds, not even across a drain. A cycle the queue proves delivered a wake
reports that wake and exits 0; a cycle whose records a handling turn already
drained reports the delivery without inventing a reason line; only a cycle that
delivered nothing is still the typed nonzero failure. Fixing it in the arm covers
codex, opencode, pi, grok and kimi, not just the Claude Stop path.

Regressions: tests/fm-session-lock-ancestry.test.sh pins both platforms' ps
semantics behind a deterministic process table and runs the real Stop auto-arm in
version-named, daemon-parented, and combined real process trees, each orphaned so
the walk cannot escape the fixture. tests/fm-watch-arm.test.sh drives a real
watcher and a real attached arm through a real wake. Every fault case fails on
the previous code.

* no-mistakes(review): Bind watcher delivery records to process identity

* no-mistakes(review): Return validated watcher identity atomically

* no-mistakes(review): Track watcher successors by PID and identity

* no-mistakes(document): Consolidate watcher arm-cycle documentation ownership
* fix(supervision): harden Claude auto-arm failure handling

* no-mistakes(review): Guarantee automatic retry after Claude auto-arm failures

* no-mistakes(review): Gate attended fail-open on verified supervision failure

* no-mistakes(document): Document Claude auto-arm retry and guard scope

* no-mistakes: apply CI fixes

* fix(supervision): make Claude fail-open progression monotonic

* no-mistakes(review): Preserve auto-arm failure episodes until verified watcher recovery

* no-mistakes(review): Linearize auto-arm failure progression across existing locks

* no-mistakes(review): Linearize positive recovery across shared failure episode lock

* no-mistakes(review): Scope Claude recovery contention to Claude guard mode

* no-mistakes(document): Align supervision auto-arm documentation

* no-mistakes(review): Preserve actionable wakes despite healthy successors

* no-mistakes(document): Refresh supervision auto-arm documentation
…d#1563)

* feat(bin): require an explicit ship delivery mode in fm-brief

A ship brief's definition of done was shaped by a silent per-project registry
lookup, so an adjusted brief and the task's recorded delivery could disagree and
no one had to decide anything per task.

fm-brief now requires --mode on ship scaffolds, validates it against the closed
set, refuses the conditional no-mistakes-prod-only registry policy as a task
mode, and records the choice as a fixed machine-readable "Delivery contract:
mode=<mode>" line that fm-spawn can check. --mode is refused on scout and
secondmate scaffolds, and --yolo is refused outright because the worker never
owns approval decisions.

* feat(bin): require an explicit ship delivery contract at spawn and promotion

fm-spawn resolved every ship and scout task's mode and yolo from the project
registry, so the delivery posture was never a per-task decision and could
contradict the brief the worker was about to follow.

fm-spawn now requires --mode and --yolo on ship spawns, validates both against
their closed sets, and reads the brief's recorded delivery contract line and
refuses a mismatch before any endpoint exists; a brief scaffolded before that
line existed warns once and launches on the flag. A batch carries one shared
contract that each pair still checks against its own brief. Scout and secondmate
spawns refuse the flags, and a scout now records no mode or yolo at all, which
teardown and the snapshot already tolerate. When the explicit mode carries less
rigor than the project's standing posture, a deviation notice is printed and the
spawn continues, so the registry stays advisory rather than an enforced default.

fm-promote requires the same two flags, because a scout carries no posture to
inherit, and writes them into the task record with the kind flip.

fm-project-mode keeps its one registry parser for the mechanical consumers that
have no task in hand, accepts the conditional no-mistakes-prod-only annotation
and maps it to its most rigorous leg for them, and grows --raw so the deviation
notice can tell a conditional policy apart from a flat mode.

* docs: record the explicit per-task delivery contract

AGENTS.md section 7 now owns how each ship task's mode and yolo are resolved at
intake, including the surface classification for a no-mistakes-prod-only project
and the unregistered-project fallback, and the project-management skill defines
that conditional policy as a registration-time posture with its defaults and
initialization consequences. The registry blurb, script table, and architecture
section follow: the registry records the captain's standing posture, and task
delivery is decided per task and passed explicitly.

* test: pass ship delivery flags per call site in the Herdr launcher e2e

The shared spawn helper also launches a secondmate, which refuses the flags, so
the contract belongs at each ship call site rather than inside the helper.

* test: pass the ship delivery contract in the secondmate suites

Both suites scaffold or spawn an ordinary ship task as the control case for a
secondmate assertion, so each needs the explicit contract the ship path now
requires.
* Add generic remote secondmate transport

* Add routed remote secondmate replies

* Add remote outbox backlog handoff

* Integrate remote secondmate lifecycle

* no-mistakes(review): Fix remote snapshot and handoff races

* no-mistakes(review): Serialize remote home provisioning transactions

* no-mistakes(review): Harden remote lifecycle transaction boundaries

* no-mistakes(review): Serialize remote lifecycle mutations and fail closed

* no-mistakes(review): Close remote lifecycle and file race windows

* no-mistakes(review): Serialize remote reply retirement and inheritance

* no-mistakes(review): Harden remote transfer integrity and recovery

* no-mistakes(review): Serialize remote respawn with registry retirement

* no-mistakes(document): Document remote bootstrap convergence accurately

* no-mistakes(document): Clarify skipped remote secondmate mutations

* no-mistakes(lint): Resolve remote script ShellCheck warnings

* no-mistakes: apply CI fixes
* feat(spawn): propagate a native W3C traceparent to spawned agents

Add a default-off capability that resolves one W3C traceparent for a task,
injects it into the agent's pane shell as the TRACEPARENT environment
variable immediately before launch, and records the identical value as
traceparent= in state/<id>.meta, so an external observer that explicitly
reads that env value or meta field can correlate a worker, a Secondmate, and
their nested children into one trace with no collector, storage, UI, or
vendor coupling.

TRACEPARENT as an environment variable is a firstmate convention carrying a
W3C-formatted value: W3C Trace Context standardizes the header, not an env
var, and OpenTelemetry SDKs do not read it automatically, so a downstream
must consume it deliberately; this feature parents no SDK span by itself.

Identity is per task, not per spawn: the carrier is minted with random ids on
the first spawn, adopted as a child (fresh span, same trace) for a nested
spawn whose parent already holds one, and reused verbatim from the meta on
relaunch, so a task keeps one stable logical identity across restarts. A
malformed or all-zero inherited value is treated as absent and roots a fresh
trace. A new root is sampled (01) - a sampling decision a downstream
parent-based sampler honors, not a guarantee that any collector stores a
span, and firstmate emits no spans; a child preserves the inherited flag.

Trust boundary: a firstmate-minted root is random and reads no prompt, path,
task prose, credential, or arbitrary environment key. An inherited
TRACEPARENT is opaque caller-controlled data - up to 24 bytes of id passed
through after syntax validation - so whoever set it controls those bytes, a
bounded fixed-width channel rather than a general content or secret channel.
The feature adds no OTEL_* variable, no tracestate, and no arbitrary
environment injection; it runs no configurable or arbitrary command, only the
fixed local od and tr (resolved from PATH) to read a few bytes of entropy - a
small local pipeline with no network or watchdog and no hard latency
guarantee. Any entropy or validation failure that returns omits the carrier
without aborting the spawn. A default-off spawn leaves the generated meta and
launch environment unchanged.

Enablement is default-off (config/trace-context, or FM_TRACE_CONTEXT where a
non-empty value overrides and unset or empty defers to the file) and is
propagated into secondmate homes, taking effect at each agent's next launch:
a Secondmate launched or relaunched after enablement carries the primary
trace into its nested workers, while an already-running Secondmate roots new
traces for its own workers until relaunched. Injection reuses the existing
GOTMPDIR channel, so all spawn backends and harnesses and the ship, scout,
and secondmate paths are covered.

Covered by a pure-library suite and a spawn-path integration test (fake tmux
plus a real worktree, hermetic against ambient FM_TRACE_CONTEXT) proving the
recorded and injected carriers are identical and sent before launch, that
default-off writes and injects neither, that a relaunch reuses the recorded
carrier, and that an explicit FM_TRACE_CONTEXT overrides the file both ways;
plus a source-owner inheritance test proving trace-context propagates and
absence-mirrors through propagate_inheritable_config.

Documentation follows the repository documentation-audiences contract:
docs/trace-context.md is maintainer-architecture rationale, the configuration
schema lives in docs/configuration.md, and the repeatable test evidence is
separated into docs/verification/trace-context.md (maintainer-verification),
registered in docs/documentation-audiences.json.

* fix(spawn): propagate the effective trace-context decision to secondmates

FM_TRACE_CONTEXT overrode trace context only in the process that read it. A
newly launched secondmate decided enablement from the inherited
config/trace-context file alone, so the override did not cross the
primary-to-secondmate boundary: FM_TRACE_CONTEXT=off with the file present left
the secondmate's nested workers traced (a broken kill switch), and
FM_TRACE_CONTEXT=on with the file absent left them untraced despite the
inherited carrier.

Deliver the primary's effective decision to a newly launched secondmate as a
normalized on/off FM_TRACE_CONTEXT in the launch prefix, so a FM_TRACE_CONTEXT
override governs the nested primary -> secondmate -> worker chain both ways, not
just the copied file. The value is bounded to the literal on/off and does not
broaden environment injection; the already-running secondmate boundary is
unchanged.

Add a genuine two-level spawn regression that drives fm-spawn twice with the
exact environment the primary injects into the secondmate and proves both
divergent directions end to end. Correct the documentation that implied
secondmate coverage on every backend, since orca and cmux reject secondmate
spawns, and refresh the verification evidence for the new assertion count.

* no-mistakes(review): Clarify Secondmate trace-context launch snapshots

* no-mistakes(document): Correct trace-context documentation ownership and relaunch semantics

* fix(spawn): resolve the trace-context decision once for carrier and snapshot

The effective trace-context decision was read twice per spawn: once inside
fm_trace_context_resolve for the recorded carrier, and again for the secondmate
FM_TRACE_CONTEXT launch snapshot. A config-file change between the two reads
could pair a carrier with the opposite enable state - an injected carrier with
an off snapshot, or no carrier with an on snapshot.

Freeze the effective on/off decision once, drive the carrier resolution under
that frozen FM_TRACE_CONTEXT so it cannot independently re-read the file, and
reuse the same frozen decision for the secondmate launch snapshot. Add a
spawn-path regression that drives the file-decided path and proves the recorded
carrier and the delivered snapshot always agree, and refresh the verification
evidence for the new assertion count.

* no-mistakes(review): Preserve legacy Secondmate trace boundary

* no-mistakes(document): Correct trace-context verification comparison base

* no-mistakes(review): Captain, prevent failed trace delivery metadata claims

* no-mistakes(review): Captain, align trace-context tests and verification evidence

* no-mistakes(document): Correct trace-context verification evidence

* no-mistakes(lint): Suppress intentional ShellCheck literal-dollar warnings

* no-mistakes(review): Captain: freeze trace context at session start

* no-mistakes(test): Captain: stabilize scheduler test and document Kimi trace coverage

* no-mistakes(document): Document trace-context safety boundaries

* fix(trace): fail off on stale session snapshots

Publish each home session decision atomically through a same-directory temporary file and bind it to the current session lock. A replacement failure can no longer leave an earlier on decision active in a later session; missing, stale, malformed, or unpublishable state defaults safely to off.

Add regressions for read-only replacement and failed publication, update spawn and session-start fixtures for the lock-bound format, and refresh the architecture and verification records.

* no-mistakes(review): Fix trace spawn failure independence and duplicate safety

* no-mistakes(document): Refresh trace-context documentation and verification

* no-mistakes(review): Clear partial backend input after failed trace submission

* no-mistakes(review): Stop unsafe trace delivery before launch append

* no-mistakes(document): Document unsafe trace delivery handling

* fix(trace): bound each trace to one routed task, never the routing agent

A persistent Secondmate holds its launch-time TRACEPARENT in the process
environment for its whole life, and routed requests never replace it, so
resolving new-task carriers from the ambient environment chained every
routed task into one ever-growing trace per Secondmate with distinct
parent ids. Resolve now reuses the task's recorded carrier or mints a
fresh sampled root, never reading ambient TRACEPARENT, so each routed
task is its own trace boundary while relaunch, recovery, and
scout-to-ship promotion keep one stable per-task identity.

The spawn regression models the reviewed scenario exactly: two unrelated
tasks spawned sequentially through one persistent Secondmate environment
record and inject distinct trace ids, adopt nothing from the Secondmate's
carrier, and a relaunch of the first task reuses its original carrier
verbatim.

* docs(trace): define the per-task trace boundary

The design contract is one task per trace: a persistent Secondmate is
routing infrastructure with its own agent identity, never a shared trace
root for the unrelated tasks routed through it. Root/recovery semantics
replace the removed child-inheritance path, the sampling and safety
sections drop inherited-carrier language because ambient TRACEPARENT is
never read, and the verification page records the refreshed suite
inventories including the two-task Secondmate boundary regression.

* test(trace): adopt the explicit per-task delivery contract in spawn fixtures

Rebasing onto current main brings the explicit per-task delivery contract:
ship spawns now require --mode and --yolo instead of resolving them from the
project registry. The trace spawn fixtures pass the same explicit contract
canonical spawn tests use, preserving the per-task trace boundary coverage
unchanged, and the verification page records the refreshed comparison base.
* fix(bin): classify tmux agent liveness independent of process titles

`fm_backend_tmux_agent_state` attributed a pane solely from
`#{pane_current_command}`, which is a process TITLE a harness can rewrite,
not a structural fact. Claude Code 2.1.220 reports its version string there,
so a live Claude endpoint classified `ambiguous`: the session-start secondmate
liveness sweep could no longer see it, and any consumer that gates on a
positive classification refuses outright.

Read a second, independent name source: the kernel `comm` of every process in
the pane tty's foreground process group. Either source naming a verified
harness yields `alive`, because a false `dead` is the one verdict that can
start a duplicate agent on a live worktree. Scoping to the foreground process
group rather than the pane's descendants keeps a harness-named background
process from faking an agent, and covers multi-process launchers (the Pi
Launcher path) without a special case.

Verified on 2026-08-03 against all seven adapters running for real on tmux
3.6a / macOS 26.5.2 arm64: claude 2.1.220, codex-cli 0.146.0, opencode
1.18.11, pi 0.82.0, pi-signed 0.82.0, grok 0.2.118, kimi 0.31.1 all classify
`alive`, each attributed by a source independent of its title.

Two tests, because they fail for different reasons:
- tests/fm-tmux-agent-liveness.test.sh pins the logic with real processes and
  no harness, so it runs everywhere CI runs tmux. It drives the two name
  sources apart on purpose and asserts the divergence, so no case can go
  quietly vacuous.
- tests/fm-harness-liveness-drift-live-e2e.test.sh relaunches every installed
  harness and fails naming the harness and version when one stops being
  attributed by a title-independent source.

AGENTS.md section 4 carries the resulting standing rule, and
firstmate-coding-guidelines owns how to satisfy it.

* no-mistakes: apply CI fixes

* docs: move the harness-dependent-check policy out of AGENTS.md

The standing rule was stated in AGENTS.md section 4 with the mechanics in
firstmate-coding-guidelines, which split one contract across two owners and
charged every session for a rule that only fires when firstmate's own
harness-dependent code is being changed.

firstmate-coding-guidelines is now the single owner of both the rule and how
to satisfy it: real-harness proof required, that proof authorized to spend
tokens, structural signals preferred over vendor-rendered surfaces, and a
guard that fails loudly naming the harness and version where a surface signal
is unavoidable. No inline stub is left behind, because AGENTS.md already
carries the load trigger for that skill in sections 7 and 13, so it is read
before any change to firstmate's shared tracked material.

Also records the cross-platform lesson the pipeline caught in the portable
regression, and corrects that file's header: the divergence assertion lives
on the version-string case, which diverges on both supported platforms,
rather than on every case.

* no-mistakes(review): Harden tmux liveness identity and drift validation

* no-mistakes(document): Clarify cross-platform tmux liveness documentation
…#1609)

* feat(bin): trace remote secondmate routes and unify the inherit allowlist

Per-task W3C trace context (kunchenguid#995) resolved and injected its carrier only at
the local spawn path. A remote secondmate is routed through
spawn_remote_secondmate, which returns long before that site and wrote its own
metadata block, so a remote secondmate stayed silently untraced even with the
capability enabled.

The parent home still owns that task's identity, because it holds the metadata
an observer reads. It now resolves the carrier against the task's own meta
under its own frozen decision - reused verbatim on relaunch, freshly rooted
otherwise, never adopting the parent process's ambient TRACEPARENT - and hands
it to the configured host through a new fm-spawn --traceparent argument,
accepted only for a secondmate launch and only as a strict W3C value. The
remote host exports it at the same unconditional pre-launch site and reports
back the carrier its endpoint actually holds, which the parent records, so an
already-alive endpoint reports the identity its agent really received rather
than one the parent merely intended. Disabled remains byte-identical and off.

The remote inherit path also carried its own hardcoded copy of the inheritable
config set, already drifted from FM_INHERITABLE_CONFIG by trace-context. Both
remote ends now derive from that one declaration, so a future item cannot be
sent by one side and refused by the other, and session-scoped enablement items
are skipped on live convergence exactly as the local path skips them.

Also fixes a latent stderr leak: an absent session lock printed a raw redirect
failure, which the new remote resolve site made visible.

Adds tests/fm-remote-secondmate-trace-context.test.sh, driving the real
parent -> fm-on -> remote entrypoint -> control -> remote fm-spawn chain over
the deterministic SSH boundary and reading the carrier back from the remote
pane's own log.

* no-mistakes(document): Clarify remote trace and allowlist contracts
* feat(bin): widen the remote runtime PATH and add a remote doctor preflight

The fixed remote entrypoint hard-coded a four-directory PATH, so a remote
account whose tools live under nix or a per-user profile could not run basic
Firstmate work without a login shell. The entrypoint now composes its child
PATH from the code root's bin, the account's ~/.local/bin, the common
package-manager directories that actually exist on the host, and the portable
system tail, deduplicated and in a fixed order, still under env -i with the
same variable allowlist and no shell command string.

fm-remote-doctor.sh reports that exact PATH by inheriting it from its own
entrypoint launch rather than recomposing it, so the ordering keeps one owner.
It is read-only, reports where each required and optional tool resolved, and
exits non-zero naming every required tool that did not. Remote seeding runs it
as a preflight before anything is created on the host and restores the registry
when it fails.

* no-mistakes(review): Harden remote git authorization and missing-tool diagnostics

* no-mistakes(document): Document remote PATH doctor and safe shims

* no-mistakes(lint): Fix ShellCheck findings in remote path tests

* no-mistakes(lint): Suppress exported fixture's false-positive ShellCheck warning
* feat(bin): gate remote second mates on herdr readiness

A remote second mate now always runs on the Herdr backend, whose server
belongs to the host's GUI login session and therefore outlives the SSH
connections that supervise it. fm-spawn's remote route forces that backend
and the host-local control script refuses any other, so the requirement
cannot be dropped from either side.

fm-remote-doctor.sh becomes the single owner of what "ready" means. It keeps
its PATH and tool reporting from kunchenguid#1623 and adds the Herdr, Aqua LaunchAgent,
GUI-session, server-reachability, and entrypoint-symlink checks, tagging each
gap fixable: or human: with the exact operator step. --fix closes only the
automatable gaps - writing and loading the Aqua-scoped dev.firstmate.herdr
launch agent, starting the server where no launch agent applies, and
recreating the entrypoint symlink - then re-derives every check from the host,
so a human gap is never presented as fixed. It never creates a login session,
writes an auto-login password, or touches FileVault.

Remote seed, remote spawn, and the startup liveness relaunch all run the same
check, repair, re-check sequence through one shared library and fail closed
with the doctor's own gap text. Recovery inherits the gate because it respawns
through the same route.

Tests drive the real doctor against a controlled account fixture with a
private HOME, a state-backed launchctl, and a fake herdr, and prove the
dangerous actions are never attempted. The remote lifecycle suites gain a
stateful Herdr CLI fixture and answer the readiness gate at the SSH boundary,
so they never inspect or repair the runner's own account.

* no-mistakes(review): Validate launch-agent contract and confirm Herdr startup

* no-mistakes(review): Validate loaded launch-agent contract before readiness

* no-mistakes(review): Refuse legacy remote backends without altering routes

* no-mistakes(review): Clarify conditional remote readiness repair sequence

* no-mistakes(review): Repair remote readiness before liveness probing

* no-mistakes(review): Preserve unknown seeds and reject legacy liveness

* no-mistakes(document): docs: clarify remote Herdr backend ownership
…1659)

* Pin remote secondmates to fm-remote

* no-mistakes(review): Fail closed on legacy remote Herdr endpoints

* no-mistakes(review): Isolate fm-remote launch agent from interactive default

* no-mistakes(document): Document shared remote Herdr retirement safety
)

* feat: run remote commands through Aqua job worker

* no-mistakes(review): Enforce remote job deadlines and safe worker shutdown

* no-mistakes(review): Refresh stale workers and harden dependency-free supervision

* no-mistakes(review): Harden worker ownership recovery and shutdown quarantine

* no-mistakes(review): Fix doctor bootstrap, harness repair, and output draining

* no-mistakes(review): Probe doctor tools through authenticated worker bootstrap

* no-mistakes(review): Refresh stale workers before doctor tool probes

* no-mistakes(review): Recover stopped quarantines and extend job deadlines

* no-mistakes(review): Separate queue and execution timeout windows

* no-mistakes(review): Supervise Linux worker crashes and bind root identity

* no-mistakes(review): Resolve authorized Nix profile bin links

* no-mistakes(review): Clarify Nix path resolution documentation

* no-mistakes(review): Harden PATH safety and nvm selection

* no-mistakes(review): Honor nvm system defaults and refresh doctor digest

* no-mistakes(review): Keep workers ready during active jobs

* no-mistakes(review): Bound pre-execution validation by job timeout

* no-mistakes(document): Clarify remote worker documentation

* no-mistakes(lint): Fix remote worker ShellCheck diagnostics

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes
* fix(remote): arm SSH dead-peer detection in fm-on.sh

A vanished remote host mid-poll (a reboot, a dropped link) left ssh
blocked indefinitely on a half-open TCP connection, because fm-on.sh's
ssh invocation had no ServerAliveInterval/ServerAliveCountMax. This
wedged the remote-reply ferry: fm-procevent.sh's runner blocked inside
the ssh child and never reached its own no-result -> claim-release ->
reconcile re-arm self-healing path, which otherwise already handles a
nonzero exit with empty output correctly. Recovery required a manual
retire and re-arm.

Arm ServerAliveInterval=15 and ServerAliveCountMax=3 by default
(bounded ~45s detection window), both overridable via
FM_SSH_ALIVE_INTERVAL and FM_SSH_ALIVE_COUNT_MAX. This is a transport-
level fix in fm-on.sh, so it covers every remote command routed
through it, not just the reply ferry. The remote sshd answers
keepalive probes independently of whatever the remote command is
doing, so a legitimately long-but-alive command (a 55s poll, a clone,
the doctor) is never falsely killed - only a truly vanished peer trips
it, turning that case into a bounded, detectable ssh failure (exit
255) instead of an indefinite hang.

Extends tests/fm-on.test.sh with a behavioral regression asserting a
bounded, positive ServerAliveInterval/ServerAliveCountMax on the real
ssh argv captured through the FM_SSH_BIN process seam, plus coverage
that both are env-overridable.

* no-mistakes(document): Document SSH dead-peer detection ownership
* feat(bootstrap): gate stale axi CLIs at the floors firstmate actually uses

Add gh-axi 0.1.29 floor so bare --squash PR merges stop failing quietly on
older builds. Raise tasks-axi to FM_TASKS_AXI_MIN=0.2.2 (multi-id mv) while
keeping feature probes. Keep quota-axi at 0.1.16 after verifying schema 3
and per-model availability already ship there; runway remains optional.

* no-mistakes(document): Clarify AXI compatibility documentation ownership
…d#1661)

* fix(guard): stop false watcher-down alarm mid-turn under Claude auto-arm

bin/fm-guard.sh derived its watcher-health verdict from fm_watcher_healthy,
which requires a live watcher process holding the home lock. Under the Claude
Stop-hook auto-arm supervision model the watcher is armed at each turn end and
exits on its wake, so it runs only between turns. Every guarded command run
mid-turn therefore found no live watcher and printed the "WATCHER DOWN -
SUPERVISION IS OFF" banner even though supervision was healthy. Because the
episode key was derived from the beacon mtime (which the between-turns watcher
advances every poll), the full banner re-printed on essentially every command,
and the message always blamed a "fresh beacon" that was in fact fresh.

Make the pull guard's health check model-aware via a new
fm_watcher_supervision_verdict in bin/fm-wake-lib.sh:

- Under the auto-arm model a beacon fresh within FM_GUARD_GRACE is healthy even
  with no live watcher process; only a beacon stale beyond grace (or absent) is
  a genuine lapse and alarms.
- Under every persistent-watcher harness (codex foreground checkpoint,
  opencode/pi/grok background arm, tmux, unknown) a live identity-matched
  watcher with a fresh beacon is still required, unchanged.

The banner now names the true failing condition, a missing live watcher process
versus a genuinely stale beacon, instead of always blaming the beacon, and the
once-per-episode dedup keys on that condition rather than the beacon mtime so a
genuine lapse announces once and does not re-print each turn.

The turn-end guard keeps the strict fm_watcher_healthy check because it fires at
the turn boundary, where the auto-arm brings a fresh watcher up and it
cooperates with that arm. fm_watcher_healthy itself is unchanged, so the arm
layer's start/attach/replace decisions are unaffected.

Tests in tests/fm-guard-stale-banner.test.sh cover the auto-arm healthy
fresh-beacon-without-a-watcher case, the auto-arm stale-beacon alarm and its
stable episode, the true-reason banner wording, and the reason-keyed episode
surviving a beacon mtime change; existing persistent-model cases are pinned to
that model.

* no-mistakes(review): Pin secondmate supervision model to launched harness

* no-mistakes(document): Align watcher documentation with model-aware supervision health
* fix(tests): stop fixture-tempdir helper from self-deleting under command substitution

fm_test_tmproot is almost always called as `TMP_ROOT=$(fm_test_tmproot prefix)`,
which forks a subshell to capture its stdout. The old implementation set its
EXIT cleanup trap inside that call, so the trap fired - and deleted the fixture
root - the instant the subshell exited, before the real caller's own EXIT trap
was ever installed. Every test using the documented call pattern leaked its
fixture root on every run; two suites had already independently discovered and
worked around this with ad-hoc mktemp calls.

Registration now goes through a $$-keyed registry file instead of in-process
state, since $$ resolves to the invoking shell's PID even inside the
subshell. The real cleanup trap is armed once at source time (always the real
caller, never a subshell) for EXIT, INT, and TERM. A best-effort orphan sweep
on next source reaps marked fixture roots old enough to be from a killed prior
run.

Simplifies the two existing ad-hoc workarounds (fm-procevent.test.sh,
wake-helpers.sh) back onto the shared helper now that it works correctly.

* no-mistakes(review): Preserve live fixtures during orphan reaping

* no-mistakes(review): Harden fixture ownership against PID reuse

* no-mistakes(review): Secure cleanup registry against path precreation

* no-mistakes(review): Make fixture registration transactional

* no-mistakes(document): Documentation already matches fixture cleanup behavior

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes
* feat(herdr): default presentation spaces on with an explicit opt-out

Herdr's disposable one-task presentation workspace was opt-in through the
presence of local config/herdr-presentation-spaces. It is now on by default,
and a home opts out by writing "off" into that same file.

Values are read with the whole-file whitespace-stripped convention the other
scalar config items already use, plus case folding. An absent file, an empty
file, and "on" all resolve on; only "off" opts out; an unrecognized value warns
and keeps the default rather than failing a spawn over a purely visual setting.
The empty file is exactly the historical opt-in form, so every home that had
already enabled the projection stays enabled with no migration step, and no
previously enabled home can be turned off by the flip.

Because absence now means on at both ends, secondmate inheritance needs no
item-specific convergence: mirroring an absent primary file converges a
secondmate to the same default-on rather than turning its projection off, and
only an explicit primary opt-out propagates the opt-out.

The gate itself moves into fm_backend_herdr_presentation_enabled in the Herdr
adapter so the semantics have one owner that regressions can exercise directly.

* no-mistakes(document): Document Herdr default-on presentation safety

---------

Co-authored-by: kunchenguid <kun-1@kunchenguid.com>
…henguid#1711)

* fix: surface consolidated open decisions on every wake-drain

A needs-decision or blocked event buried under later, unrelated status
appends was only ever shown via the last-line wake annotation, so a
still-open captain decision could go silently missed even though
status_open_decisions (fm-classify-lib.sh) already folds the whole
status stream correctly and fleet-snapshot/bearings already reuse it.

Wire that same fold into bin/fm-wake-drain.sh: a new fleet-wide
scan_open_decisions wrapper scans every state/<id>.status, and
fm-wake-drain.sh prints a separate, bounded OPEN DECISIONS section on
every drain (including the empty-queue fast path), so session-start
and every wake-handling turn surface it for free without duplicating
the open/resolved fold itself. Heartbeat wakes drain through the same
script, so this covers that surface too.

Also tighten status_open_decisions' file guard to skip an unreadable
status file instead of leaking a bash redirection error, now that a
fleet-wide directory scan can reach files a single targeted read
would not.

* no-mistakes(review): Prevent status symlinks leaking open decisions

* fix: drop unbounded perl subprocess from status symlink guard

The review step's own symlink-safety auto-fix (O_NOFOLLOW read via a
perl subprocess) forked one perl process per status file scanned by
the new fleet-wide open-decisions scan, with no cap - inflating
fm-wake-drain.sh's total external-read cost from 8 (the existing
annotation read_cap) to 18 in the enrichment-caps regression test.

The plain [ -L "$f" ] check already rejects any status file that is
itself a symlink before any read happens, which is exactly what the
new regression test exercises and is the same defense level the
sibling scan_captain_relevant_statuses/last_status_line already rely
on elsewhere in this file (no O_NOFOLLOW). Drop the subprocess-based
nofollow read and keep the cheap builtin guard.

* no-mistakes(document): Document actionable fleet-wide open decision drains
…kunchenguid#1710)

* fix(bin): abort orphaned no-mistakes runs and reap leaked processes at teardown

Teardown could remove a task's worker while its no-mistakes pipeline run was
still parked at a gate, leaving an orphaned run holding a fleet slot
indefinitely (observed 2026-08-03: runs parked 7h39m and parked at a
post-CI approval gate). It could also leave backgrounded/disowned
descendant processes rooted under the worktree or tasktmp surviving
reparented to init (observed: two `go test` binaries pinning CPU for
hours with no live task meta to attribute them to).

Add two coupled pre-teardown steps, both scoped to this task's exact
branch/head or worktree/tasktmp so they can never touch another task's
run or processes:
- conclude_task_no_mistakes_run aborts a run parked at a gate via
  `no-mistakes axi abort`, cd'd into the exact worktree so the daemon
  resolves the run itself rather than teardown naming a --run id.
- reap_task_worktree_processes sweeps for processes whose cwd is under
  the worktree or tasktmp (via `lsof -a -d cwd`) and TERM/KILLs them.

Both run before any worktree return, branch delete, or backend kill,
and are idempotent on a retried teardown. The branch+head attribution
logic is factored out of bin/fm-crew-state.sh into the new shared
bin/fm-nm-run-lib.sh so both scripts use the same ownership contract.

* no-mistakes(review): Fail closed on incomplete teardown cleanup

* no-mistakes(review): Bind teardown cleanup to verified run and process identities

* no-mistakes(review): Require confirmed aborts and convergent identity-safe process reaping

* no-mistakes(review): Handle process exits during teardown identity checks

* no-mistakes(review): Restore teardown library in hermetic gotmp fixtures

* no-mistakes(document): Document teardown run attribution and timeout

* no-mistakes(lint): Rename shell variable conflicting with done keyword

* no-mistakes: apply CI fixes
kunchenguid#1709)

The script installs as a symlink under ~/.local/bin. Taking dirname of
the symlink itself (instead of its real target) pointed SCRIPT_DIR at
~/.local/bin, breaking sourcing of the sibling fm-remote-job-lib.sh.
Resolve the real path first, preferring python3's os.path.realpath,
then realpath, falling back to the raw BASH_SOURCE on hosts with
neither.
…d#1724)

* fix(pi): stop Calm claiming a built-in tool name another extension owns

fm-calm.ts claimed bash/read/edit/write/grep/find/ls unconditionally at
extension load, regardless of whether Calm was on. Pi resolves two
extensions registering the same built-in name by first-registered-wins
with no merge and no unregister call, and Calm's project-local
.pi/extensions/ position beats any global or CLI-configured extension,
so a user who never even enabled Calm could have their own bash/read/etc
override silently replaced.

Captain-approved plan implemented:

- Registration is now gated on config/calm already being "on" at load
  time. A Calm-off session or reload registers nothing, so a non-Calm
  user never contests a name. This stays synchronous during the
  factory's own load, not deferred to session_start: /reload (and
  ctx.newSession/fork/switchSession) render the restored transcript from
  a pre-session_start snapshot of the tool registry, so a deferred claim
  would miss that render - confirmed by tests/fm-calm-pi-extension
  .test.sh's hidden-block-geometry E2E when trialed.
- The first time Calm turns on in a session that started off
  (activateBuiltInsIfNeeded, from the /calm command handler), Calm calls
  pi.getAllTools() - safe only once every extension has finished loading,
  unlike the load-time path above - to see whether a different extension
  already owns a name, and skips claiming only that one, leaving it and
  its owning extension fully intact and callable.
- A contested name found this way prints a prominent ctx.ui.notify()
  warning naming the tool, plus a console diagnostic.
- reportBuiltInLosses() remains the backstop for the one case neither of
  the above can reach: a session that starts or reloads with Calm already
  on, where the registry snapshot is taken before Calm gets any chance to
  check ownership. A symlink-safe realpath comparison avoids misreporting
  Calm's own registration as foreign when its path crosses a symlink
  (macOS /tmp, /var).

Confirmed, bounded trade-off: the very first time a session that started
Calm-off turns Calm on, tool-call rows already on screen from before that
toggle do not retroactively collapse, because Pi never lets an extension
re-point an already-rendered row at a definition registered later. Every
session after that first toggle starts with the preference already on and
takes the synchronous load-time path, so the guarantee is intact from
then on. docs/calm.md and the file's own header document this in full.

tests/fm-calm-pi-extension.test.sh gains test_builtin_gate_load_time
(config/calm off registers nothing, on registers all 7 synchronously at
load) and test_calm_activation_collision_and_regression_bound (first
activation claims every uncontested built-in, leaves a foreign bash tool
fully intact and callable, warns and logs the contested name, and locks
in the documented pre-activation bound against real ToolExecutionComponent
rendering). test_rendering_and_session_lifecycle and the live interactive
E2E are updated for the new gate-at-load and first-activation-bound
contract.

* no-mistakes(document): Document Calm tool collision boundaries

* no-mistakes: apply CI fixes
…#1727)

* fix(bin): give secondmate homes a durable parent binding record

Finished-worker cleanup on a remote second mate refused forever with
"cannot resolve the primary home ... durable parent binding". The
remote launch hands the child the remote code checkout as its parent
home (fm-spawn.sh's sole writer of FM_PUBLIC_FOLLOWUP_PRIMARY_HOME
receives FM_HOME=$FM_ROOT from fm-remote-secondmate-control.sh's
host-local launch), and that path can never carry the parent's real
records, so the guard refused unconditionally once relay looked active
anywhere on that host.

fm-home-seed.sh and fm-remote-home-provision.sh now write a durable
.fm-secondmate-parent record next to the .fm-secondmate-home identity
marker, naming the home's route to its parent as local (with the real
parent path) or remote (with the parent's SSH alias for diagnostics
only). fm-teardown.sh's cleanup gate reads it: a remote parent is out
of scope for the delegated-promise check (the whole promised-public-
reply subsystem is same-filesystem by construction, so a remote parent
can never hold one), while a token committed directly to the child's
own .env file - never the process environment - still refuses, so an
unrelated export in the remote host's login shell can no longer mask
in. For a local secondmate, the durable parent_home now also backs up
the launch-time env var, closing a silent fail-open where a restart
that dropped the launch prefix made the guard treat a genuinely active
parent relay as off.

Regression coverage drives the real remote route (SSH boundary + Herdr
fixture) and real fm-home-seed.sh seeding rather than hand-crafted
markers.

* no-mistakes(review): Captain: fail closed on unsafe durable parent records

* no-mistakes(review): Captain: enforce durable parent binding commit protocol

* no-mistakes(review): Captain: publish local parent binding before identity

* no-mistakes(review): Captain: refuse conflicting local parent bindings

* no-mistakes(review): Captain: reject non-regular secondmate seed leaves

* no-mistakes(review): Captain: enforce unique durable parent bindings

* no-mistakes(review): Captain: reject route-incompatible durable parent fields

* no-mistakes(document): Document durable secondmate parent bindings

* no-mistakes(lint): Fix secondmate parent parser ShellCheck warnings

* no-mistakes: apply CI fixes
* feat(bin): gate lavish-axi at its session_ended floor in bootstrap

bin/fm-procevent-lavish.sh decides that a human "Send & End" review is
terminal by reading session_ended from the poll response's leading session
block. That field first shipped in lavish-axi 0.1.35, so an older installed
build silently leaves every ended review source armed forever and captures
an empty ended result on each later cycle. The same release is what makes a
plain reopen refuse a session the human deliberately ended.

Add LAVISH_AXI_MIN=0.1.35 to the existing axi-family floor structure in
bin/fm-bootstrap.sh, reusing tool_version_at_least and the same MISSING
diagnostic gh-axi already emits, so an incompatible build is reported as an
upgrade request before any review surface is armed. Later lavish-axi
releases only add artifact-authoring surface the adapter never reads, so
the floor is the feature-introduction point rather than latest.

Fixtures that stubbed lavish-axi as a bare exit-0 tool would now be read as
unparseable builds, so tests/lib.sh gains fm_fake_version_tool and every
bootstrap-running suite uses it for lavish-axi.

* no-mistakes(review): Clarify lavish-axi version floor rationale

* no-mistakes: apply CI fixes

* feat(bin): set axi-family floors to current latest under the bump policy

The axi-family bootstrap floors are the CURRENT LATEST published version of
each tool, captain-bumped periodically to move the whole fleet onto the
newest axi tools. They are not the minimum feature-introduced version. The
earlier lavish-axi work set a feature-minimum floor, which is the opposite
of this policy, so replace it along with the older feature-minimum rationale
carried by tasks-axi and quota-axi.

State the policy explicitly in bin/fm-bootstrap.sh's header, which owns it,
and in each per-tool floor owner, so no future change argues a floor back
down to the earliest release that happens to satisfy some behavior. Remove
the lavish-axi session_ended and upstream-PR citation, the tasks-axi
multi-ID-mv minimum argument, and the quota-axi credential-source argument
as floor rationale; the tasks-axi feature probes remain as a separate
defense-in-depth concern.

Floors: lavish-axi 0.1.45 (was 0.1.35), tasks-axi 0.2.4 (was 0.2.2),
quota-axi 0.1.17 (was 0.1.16), gh-axi 0.1.29 unchanged and already latest.
Each was verified against the tool's current published version.

The mechanism is unchanged: the same shared version helper and the same
MISSING diagnostic path. The below-fires and at-or-above-silent regression
rows move to the new floors, keeping each boundary genuine by pinning the
patch immediately below each floor rather than a version that was only
below the old one. Fleet fixtures move to the new floors so a bootstrap-
running suite is not reported as an out-of-date build.

Three operator-facing backlog handoff and receipt errors named "0.2.2+"
while the enforced floor moved, so they now point at the floor's owner
instead of duplicating a version number that drifts.

* no-mistakes(review): Centralize AXI floor policy beside constants

* no-mistakes(review): Clarify bootstrap boundary test comment

* no-mistakes(document): Centralize AXI floor policy rationale
…guid#1737)

* fix(bin): bound OPEN DECISIONS scan cost with a per-status-file cursor

The fleet-wide OPEN DECISIONS scan added in kunchenguid#1711 re-reads and refolds
every task's entire lifetime status log on every drain, so its cost
grows unbounded with total log size. Add status_open_decisions_incremental
and scan_open_decisions_incremental to fm-classify-lib.sh: they persist a
per-status-file byte cursor plus the folded open-decision set, and fold
only newly appended bytes on each call, reusing status_open_decisions'
exact fold-line rule (extracted into _fm_decision_fold_line) so the two
strategies can never disagree on what is open. A missing or invalidated
cursor (new task, truncated/rewritten/shrunk log) falls back to a full
re-fold. bin/fm-wake-drain.sh now calls the incremental wrapper instead
of the whole-file scan.

* fix(bin): add O(1) rotation detection and read-failure guarding to the cursor fold

Add the two pieces the incremental open-decisions cursor was missing,
scoped to this repo's actual status-file usage (create-once, append-only,
never replaced or rewritten in place):

- An O(1) device+inode identity check (one stat call) alongside the
  existing size-shrink check, so a status file replaced/rotated/recreated
  at the same path is detected and falls back to a full re-fold, even
  when the replacement is the same size. A same-inode, same-size,
  in-place byte edit is a deliberately accepted gap: no code path in
  this repo ever does that to a status file.
- Checked reads: a stat/wc/tail failure is a genuine I/O error, not
  "the file is empty" - it now reports the already-trusted persisted
  open set unchanged instead of risking a silent invalidation.

Both stay O(1) plus new bytes per call, matching the cursor's bounded-
cost design; no content hashing or pending-fragment machinery.

* no-mistakes(review): Preserve cursor state across failed incremental reads

* no-mistakes(review): Refold status when cursor cache reads fail

* no-mistakes(document): Document cursor-backed open-decision scanning

* no-mistakes: apply CI fixes
…guid#1754)

* fix(bin): preempt remote reply long-polls for queued short jobs

Session start on a home with live remote second mates could stall silently
for many minutes: the single serial remote job worker ran each armed
fm-remote-delta-read.sh reply poll to its full 55s window while bootstrap's
short sync, inherit, state, and route commands sat queued behind it, and
non-FIFO queue pickup let re-armed polls keep winning the lane. Measured
end to end, a trivial short job took 31s behind one 30s poll window.

The worker now preempts a running preemptible job (the read-only, cursor-
anchored delta read is the only member of that class) as soon as a
non-preemptible job is queued, publishing exit 75 with emptied output -
byte-identical to the poll's own elapsed-window-with-no-data result - so
the parent runner takes its existing no-result path and the watcher re-arms
from the same cursor with nothing lost. The delta read translates SIGTERM
into that same exit after removing its staging directory. Sibling polls
never preempt each other, so two armed monitors cannot churn. The same
measured scenario now completes in 1s.

* no-mistakes(document): Clarify remote poll preemption documentation
…#1778)

Discord mentions already ride the same pairing-token opt-in, relay poll,
and platform-aware reply path as X mentions, but the docs still read as
X-only, so a stranger could not self-serve the Discord path.

Add the numbered turn-on steps to the X mode configuration reference,
pointing at the myfirstmate dashboard for account creation, bot install,
and token issuance rather than duplicating operator setup here, and drop
the X-only framing from the README bullet, the documentation index, and
the architecture overview.
…#1781)

* feat(bin): run session start deterministically on hook-capable harnesses

Session start relied on a native nudge that only asked the agent to run
bin/fm-session-start.sh, and an agent can defer that. Observed 2026-08-01:
an /ahoy-first session followed the recap path and did not take the helm
until a later request forced it.

Claude, Codex, and Pi now RUN the digest in their session-open hook through
the new bin/fm-sessionstart-run.sh, so the full ordered digest is in model
context before the first turn. That wrapper is the single owner of what a
session-open source means: startup and Pi's "new" take the helm, clear and
compact re-emit, resume/reload/fork delegate to the nudge, and an unreadable
source takes the helm because doing that redundantly is idempotent while
skipping it is the bug. Grok and OpenCode keep the nudge as the floor, since
neither can carry hook stdout into a model turn.

Because the hook now blocks session initialization, fm-session-start.sh
bounds itself first. Its steps are not all individually bounded - bootstrap's
gh auth probe, tool version probes, the backlog listing and per-task endpoint
reads are unbounded - so the whole digest runs as one bounded child (default
120s). Whatever it emitted before the bound survives, and the parent adds a
loud STARTUP TRUNCATED banner naming the stage that stalled and every stage
that never ran, still exiting 0.

--reemit skips only the sweeps startup already reconciled. It still re-verifies
lock ownership and still drains queued wakes, which arrived after startup and
are the turn's work. fm-bootstrap.sh gains FM_BOOTSTRAP_LOCKED so a re-emit
keeps repair ownership instead of deferring to a lock holder that is itself.

Also adds bin/fm-timeout-lib.sh as the single owner of bounded execution,
replacing three near-identical copies, and gives the ahoy skill a helm check
so a nudge-tier harness cannot recap before taking the helm.

Verified live on 2026-08-05 against Claude 2.1.222, Codex 0.146.0, and Pi
0.82.0; docs/verification/supervision.md records the per-harness source
vocabulary, the two named gaps, and the refresh command.

* no-mistakes(review): Harden session-start completion, timeout, and Pi delivery

* no-mistakes(review): Harden completion ownership and portable timeout escalation

* no-mistakes(review): Normalize watchdog KILL exits without masking command status

* no-mistakes(review): Guarantee startup bounds and align harness delivery tiers

* no-mistakes(test): Fix Pi session-start live verification fixture

* no-mistakes(document): Align session-start documentation with deterministic hooks

* no-mistakes(lint): Silence intentional child-shell expansion lint warning

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes
* docs: rename the user-facing product name to Relay

The public-mention integration gated by the `.env` pairing token is now
called Relay across user-facing prose, covering X and Discord alike
instead of implying a single network.

Renames the product-name strings only: README, docs, the captain-facing
skill descriptions, and the AGENTS.md operating prose, including the
`X mode (.env)` and `Optional X mode` headings and every link anchor
that pointed at them. AGENTS.md section 14 carries a one-line bridge
note so the older name and the unchanged identifier spellings stay
discoverable.

Internal identifiers are untouched: `FMX_*`, `config/x-mode.env`,
`state/x-*`, `bin/fm-x-*`, the `fmx-respond` skill path,
`__FM_X_MODE_ENV__`, and `x-mode-error`. Platform references to X and
Discord as networks stay as they are, and the bootstrap-diagnostics
entry still quotes bootstrap's emitted `FMX: X mode on/off` line
verbatim because `bin/` output is out of scope for this pass.

* no-mistakes(review): Complete Relay prose rename in maintained docs

* no-mistakes: apply CI fixes
* feat(harness): add a verified muse crewmate adapter

Muse Code joins the fleet as a crewmate/scout adapter, verified live against
Muse Code 0.1.0-R708.1 in an isolated lab.

Detection matches the anchored prefix muse-bin*, because the installed launcher
execs a version-suffixed binary whose name changes on every auto-update and
whose install path carries no muse component to fall back on. The same identity
is taught to the tmux liveness classifier, without which a healthy muse pane
would have read as a dead endpoint.

Busy state folds muse's own durable session event log, bound per task by a
sessions-root/worktree sidecar. It is a pull source with no writer, so nothing
is armed and no record is ever seeded. The fold is anchored on the full run
lifecycle prefix so muse's nested cleanup "terminal" payloads cannot settle an
in-flight run, and it is depth-bounded so muse's native sub-agent logs cannot be
mistaken for the parent's. The idle half stays gated: an open run proves busy,
but a settled log reads unknown until a credentialed multi-step run proves one
turn stays inside one run.

Two findings corrected the scout report. The exec-only
--no-foreign-personal-context flag is rejected by the interactive TUI, so the
privacy control that actually reaches a pane worker is
MUSE_EXPERIMENTAL_FOREIGN_PERSONAL_CONTEXT_KILL, verified to drop the operator's
foreign personal rules while keeping the project's own AGENTS.md. And an
unauthenticated muse pane never exits, it waits on a device-code prompt, so
credentials are a spawn preflight rather than a screen check.

muse is refused for secondmates: it has no primary supervision protocol and its
hook dialect rejects the reawakening handlers that protocol needs.

Per the captain's decision, auto-update is not pinned, and the credentialed
multi-step smoke is deferred with an explicit checklist in
docs/verification/muse.md.

* no-mistakes(review): Accept Muse dispatch profiles and shared efforts

* no-mistakes(review): Bind Muse busy state to current session

* no-mistakes(review): Compare Muse workspace bindings literally

* no-mistakes(review): Harden Muse worker credentials and live signal verification

* no-mistakes(review): Cache Muse session bindings and clarify worker credentials

* no-mistakes(review): Clear Muse marker inheritance and normalize interrupt aliases

* no-mistakes(review): Verify Muse glyph effective foreground color

* no-mistakes(review): Harden Muse XDG paths, session cache, and glyph parsing

* no-mistakes(document): Document Muse adapter boundaries
kunchenguid and others added 29 commits August 15, 2026 22:10
…d mate (kunchenguid#2457)

The lightweight Relay follow-up link lives in the answering home's own
state/<task-id>.meta, so it can only bind work that home owns. When a
Relay-linked request is routed to a second mate, the task record lives in the
second mate's home, fm-x-link.sh failed with a bare "no such task ...meta", and
nothing else picked the promise up: only the soft acknowledgement was ever
posted. The typed promised-final path already supports --work-home
secondmate:<id>; the playbook simply never chose it.

- fmx-respond now states the routing rule crisply: a task in this home takes the
  lightweight link, and second-mate-routed work takes a promised-final
  commitment bound to that home, registered up front with the brief command
  carried into the routed worker's instructions.
- fm-x-link.sh refuses a task with no local record by naming the registered
  second mate whose home actually holds it and printing the promised-final
  registration command, with the exact --work-home when the match is
  unambiguous. A home with no registered second mates keeps the plain error.
- fm-backlog-handoff.sh reports, after a successful move, any moved key that
  still owes a public reply bound to main/<key>, since that binding no longer
  names the home owning the work. The move itself is never blocked.

Docs and the secondmate handoff prose follow the same rule. Tests cover the
refusal, its scoping, the unchanged local-link path, and both handoff outcomes
at the script boundary.
…verdicts (kunchenguid#2456)

* fix(skills): hint that remote secondmate liveness verdicts false-negative

fm-crew-state and fm-send routinely misreport a live remote secondmate
as dead; confirm against the pane before relaunching, and relaunch only
through fm-spawn.sh, never raw herdr pane surgery.

* no-mistakes: apply CI fixes
Pi 0.83.0 added a status line to every tool-expansion change, and Pi
updates the previous status line in place when two status messages
arrive back to back. Calm's post-export redraw cycled tool expansion on
the macrotask right after Pi printed "Session exported to: <path>", so
both expansion status lines coalesced over that confirmation and the
captain was left with no record of where their export landed.

Calm now repaints only the tool rows it presents, by invalidating each
row through the render context Pi hands its render slots, and requests
the surrounding redraw through setStatus. Neither appends to the
transcript. The repaint is still needed because Pi can re-render a row
asynchronously - the built-in edit row invalidates itself once its diff
is ready - and that re-render can land inside the window where /export
forces stock rendering.

The real-terminal /export case now asserts the confirmation is still on
screen after the redraw has settled, and that the redraw restored every
Calm-hidden row, instead of only racing the moment the confirmation
first appeared.
…nguid#2488)

* feat(stow): persist the open records a session is holding

/stow curated memory and captured session knowledge, but never touched
record state, while AGENTS.md called it an "unfinished-work sweep" and the
receipt declared the session "safe to reset" - wording that implied a
record-correctness guarantee stow does not make. A shipped PR with no
backlog item, a queued umbrella whose phases had merged, and four decision
holds left open after their answers shipped all survived repeated stows.

Add a bounded pass that files record state from the same volatile input the
rest of stow already uses: the open threads in context, minutes before the
reset destroys them. It creates a record for an unfiled thread and corrects
one the session knows is wrong, through the owning path, and states its
boundary as part of the contract - it never enumerates the backlog, lists
holds, or queries a forge, because it cannot be a reconciliation and must
not be read as one.

Correct the wording in AGENTS.md and the completion receipt so reset-safe
means what it actually guarantees: nothing this session knew was lost.

* no-mistakes(review): correct stow decision-hold inspection to read hold via tasks-axi

* no-mistakes(document): note /stow open-record persistence in README command catalog

* refactor(stow): state open-record persistence as principle, not procedure

The first version enumerated triggers, named commands, and prescribed an
ordered procedure. That is too rigid for an agent skill: it invites literal
execution of a checklist instead of judgment, and every enumerated example
is a way for the guidance to go stale.

Reduce it to the intent - before a reset, the important open work you are
holding in context must end up durably recorded rather than dying with the
session, filing what is unfiled and correcting what is stale - and let the
agent judge importance, the record, and the owning write path.

Keep the scope bound, since it is a decided contract and not a mechanic:
this covers the open work the session is holding, never a reconciliation of
durable records against repository or forge reality. The wording
corrections in AGENTS.md and the completion receipt are unchanged.
…eyed-answer path (kunchenguid#2490)

* fix(decisions): close captain holds at answer time

Firstmate had two "a decision is open" ledgers with asymmetric closing
mechanics. The live status-log ledger closes atomically at answer time,
because bin/fm-send.sh --resolve-key makes answering a decision be the
act that closes it. The durable backlog hold ledger had no such coupling:
answering and recording were two separate acts, and only the first was
forced by the workflow.

That asymmetry lost four real captain decisions. Their answers were
captured durably to disk, keyed character for character by the hold
decision keys, acknowledged, and even implemented and shipped, yet the
holds stayed open for two days and the captain was asked to re-answer
decisions already on his own disk.

Give the hold ledger the same answer-time-closure property:

- bin/fm-decision-hold.sh gains an `answer` subcommand, the hold ledger's
  counterpart to --resolve-key. It shares one unrouted close
  implementation with `decline`, so it carries every existing guard - the
  captain decision file, the active-hold requirement, retry identity, and
  the refusal to release still-routed work - and differs only in the
  resolution mode it records. `decline` keeps its stronger meaning that
  the answer routes no follow-up work at all.
- bin/fm-procevent-lavish.sh wires the channel that actually carried the
  lost answers. `arm --decisions-origin` binds a deck to the origin whose
  holds it carries, `answers` reads the structured choices out of a
  captured poll result, `close-decisions` maps each key to its hold and
  closes it through the command above, and `autohandle` lets the runner
  apply that at capture time.

Safety is preserved rather than traded away. Only rows tagged `choice`
are read, so freeform captain prose cannot forge a decision key. Closure
is confined to the one bound origin. The decision text is a pure function
of the captured result, so a replayed capture is idempotent. A hold that
is absent, already closed, or still blocking routed work is skipped and
left for `resolve`, never forced. A deck armed without the binding
touches no hold at all. And autohandle deliberately never reports full
handling, because recording an answer is transcription while acting on it
is firstmate's judgement - so the check wake still reaches the handler.

fm-send --resolve-key is untouched.

* no-mistakes(document): document state/lavish-decisions binding dir in AGENTS.md state inventory

* refactor(decisions): make keyed-answer closure one general capability

The previous pass gave holds answer-time closure but built it as bespoke
Lavish wiring: the review adapter carried the source-to-origin binding,
mapped keys to hold identities, wrote decision records, decided what to
skip, and closed holds itself. That treated a review deck as a special
decision source. It is not - it is an ephemeral discussion format that
happens to carry answers.

Collapse it into ONE general capability with one owner.

bin/fm-decision-hold.sh now owns the whole of "a keyed answer closes its
matching hold":
- `answers <origin> --source <provenance>` is the channel-agnostic
  intake. It reads key/answer/label lines on stdin, maps each key to its
  hold, and closes it through the same `answer` path, so every guard
  applies identically whatever channel the answer came from. --source is
  provenance recorded in the decision, never a behavior switch; there is
  no per-channel branch and no knowledge of chat, decks, or transports.
- `bind`/`unbind`/`binding` own the source-to-origin binding for any
  channel whose answers arrive detached from their origin.

Every channel is now an ordinary caller that only turns what it received
into keyed lines:
- bin/fm-send.sh (chat) feeds the intake for a key that names an active
  hold. This also fixes a real gap: once `complete` transfers a decision
  to its hold it closes the live status copy, so --resolve-key alone
  could never answer a transferred decision.
- bin/fm-procevent.sh feeds it generically. A bound source's captured
  result goes to `<adapter> answers <result-file>` and whatever that
  prints is piped into the intake. The runner names no adapter, parses
  no result, and carries no decision rule, so any future adapter with an
  `answers` command works with no change here.
- bin/fm-procevent-lavish.sh keeps only `answers`, which reports the
  structured choices a review captured and stops. It maps nothing to a
  hold and closes nothing; it lost ~160 lines of decision logic.

Feeding is independent of handling, so it never acknowledges a result
and never suppresses a wake - recording an answer is transcription,
acting on it stays firstmate's judgement.

The regression that proves closure now drives a FIXTURE adapter that is
not the review adapter, so what is proven is that any bound channel
reaches the intake rather than that one channel is wired specially. A
new regression drives the real fm-send over a stubbed transport for the
chat side. Every prior guarantee still holds, and fm-send's status-log
behavior is unchanged.

* no-mistakes(review): test(decisions): drop source-content grep from hold-closure regression
…mlink (kunchenguid#2512)

A Write aimed at CLAUDE.md followed the symlink and destroyed AGENTS.md.
The installer now creates and migrates to a recoverable two-line pointer file.
* fix(lint): catch malformed GitHub workflows before merge

A self-broken ci.yml cannot report its own breakage, so parse every
workflow in the local lint path that no-mistakes already runs.

* fix(lint): pin actionlint instead of Ruby for workflow lint

A self-broken ci.yml still has to fail in the local lint path, and the
named tool for that gate is actionlint, not a new Ruby runtime.

* no-mistakes(document): Clarify pinned workflow lint documentation
…d#2546)

* fix: install pinned shellcheck and actionlint on macOS and linux arm64

The installers were hardcoded to linux amd64 and sha256sum, so a Mac
dev could not satisfy the refuse-on-mismatch lint gate. Select the
official per-platform archive and checksum, and fall back to shasum -a 256.

* no-mistakes(document): Document cross-platform pinned lint installers
…uid#2548)

.no-mistakes.yaml has set test.evidence.store_in_repo: true since kunchenguid#2355, but
CONTRIBUTING.md, docs/configuration.md, and docs/architecture.md still described
the old policy of keeping evidence out of the repo in a temp directory.

The current no-mistakes behavior for store_in_repo: true is to publish each run's
test evidence to the orphan no-mistakes/evidence branch and link it from the PR
body. That branch shares no history with code branches, so evidence never enters
a pushed feature branch or the default branch, and CI's tracked personal fleet
paths rule stays accurate.

Docs only. No change to .no-mistakes.yaml or any workflow.
* docs: correct test evidence storage comment in .no-mistakes.yaml

* no-mistakes: apply CI fixes
…nchenguid#2563)

Make that a first-class option in always-loaded instructions so firstmate does not default to mediating and tearing the scout down between iteration rounds.
…chenguid#2570)

* fix(bin): report remote secondmate delivery and state truthfully

A steer to a remote secondmate crosses fm-on.sh to a host-local fm-send
leg whose unconfirmed submit read-back (verdict=pending, typically a busy
mate whose harness queues the steer) was flattened into exit 1, so the
parent printed "error: text not submitted" / "error: text not sent" and
discarded the pending-reply expectation for a steer that had actually
landed. fm-send now carries the verdict across the ssh boundary as a
documented delivered-unconfirmed exit 3: the parent reports the steer as
delivered with confirmation pending, exits 0, keeps the expectation armed
(awaiting_report), and closes --resolve-key decisions, while transport
loss (ssh 255) and real remote failures keep failing loudly with the
remote leg's stderr attached. A local unconfirmed submit now also exits 3
with an honest non-error message and still never closes a decision key.

fm-crew-state.sh and fm-peek.sh no longer read a remote mate's endpoint
through local probes (which misreported a healthy mate as "worktree gone"
/ "can't find session: remote"): both now use the true remote source over
fm-on.sh, and an unreachable or unreadable remote reads as unknown-remote,
never as gone or dead.

* no-mistakes(document): Document remote delivery and state truth

* no-mistakes: apply CI fixes
* Adopt quota-axi 0.1.29 spendPriority-primary array dispatch.

quota-axi 0.1.29 publishes schema 5 with selection.spendPriority as the primary comparative signal and demotes derivation fields out of default --json. Rank comparable-fit candidates on that scalar, keep runway versus the completion horizon as a hard gate, and raise the compatibility floor so a pre-consolidation build cannot reach dispatch intake.

* no-mistakes(review): Correct schema fixtures and remove prescriptive selection prompts

* no-mistakes(document): Correct quota verification evidence chronology

* Collapse quota-array-dispatch onto TOON-first spendPriority ranking.

Decide from quota-axi's default TOON; keep --json as a rare defensive fallback.
Rank by spendPriority after eligibility, reasoning-class, and runway-feasibility gates, and drop the hand-computed Pareto, pace, reserve, and window-id layers.

* no-mistakes(review): Permit ambiguous JSON fallback and correct reset fixtures

* no-mistakes(review): Correct runway semantics and escalate unresolved uncertainty

* no-mistakes(document): Document TOON-first quota dispatch evidence
* docs: add GROK_BOT.md Grok Bot system prompt

* docs: amend GROK_BOT.md with charter report-back and delegation marker

* docs: classify GROK_BOT.md as public-product

* docs: make GROK_BOT.md the plain Grok Bot system prompt
Refine language for clarity and consistency in instructions.
…#2595)

* fix(bin): guarantee inactive-reconcile scan progress under second quantization

The inactive-outcome scan computed its aggregate deadline in whole seconds,
so a 1-second budget's effective value lands anywhere in (0,1]; a scan
starting just before a wall-clock second boundary rounded its whole budget
away mid-scan and exited having visited no child, while the durable cursor
had already advanced past the never-examined child. This is the CI flake
behind tests/fm-inactive-reconcile.test.sh's 'next bounded scan did not
resume with the following child' (watcher-wake-lock family, portable
serial 2, seen on the PR kunchenguid#2590 run).

Every scan now visits at least its first due child with the per-child
state-read bound floored at one second, so no invocation can be a zero-work
no-op. The outer process-group kill moves to budget+1s: the scan's own
deadline enforces the budget, and the kill is a backstop for a scan wedged
in an unbounded wait instead of a racer that routinely preempts the clean
bounded exit. The wake-lock-wait test bound tracks the backstop (3s -> 4s);
the previously flaky assertion is unchanged.

* no-mistakes(document): Document inactive-reconcile deadline backstop
Refactor the guidelines for Firstmate's role and delegation process, emphasizing the importance of crewmates and asynchronous work.
Clarified guidelines for handing off work to crewmates and managing secrets.
…tions deterministic (kunchenguid#2617)

Three assertions in tests/fm-procevent.test.sh depended on a detached runner
having finished work that the command starting it does not wait for.

reconcile's replacement runner is started through detach_runner, which only
forks: reconcile returns and counts the start before that runner has claimed
its source or exec'd its child. Any assertion taken straight after reconcile
therefore samples a race.

- The publish-before-apply recovery section left its always-ready /bin/echo
  source registered across the recovery reconcile, so that reconcile launched
  a competing detached poll (observed: started=1) that then raced every later
  assertion for the source claim, the next capture sequence, and this home's
  applied record, and outlived the section holding a live claim. It is now
  retired before that reconcile - re-announcement is proven from the durable
  inbox alone and needs no registration - and started=0 is asserted so a
  competing poll cannot be reintroduced unnoticed. This is the same
  retire-before-reconcile discipline the self-announcing section already
  carries; that section acquired it after the identical race made its
  "not-autohandled: self-src" assertion read "already owned: self-src".

- The crashed-leader replacement section snapshotted the replacement's claim
  file and execution log behind a fixed 0.5s settle window. On a loaded
  machine that window expires first, which is the CI flake behind "a
  replacement runner started without recording its own claim" and "reconcile
  did not start exactly one replacement source". Both effects are now waited
  for with the suite's bounded wait helpers; the exact one-replacement count
  is still asserted afterwards, unchanged.

- The duplicate-start section slept 0.5s for reconcile's runner to record
  ownership before asserting that a second start loses to it. It now waits
  for that claim.

Also tighten one assertion that could not fail as written: "autohandled:
self-src" is a substring of "not-autohandled: self-src", so the applied path
was accepted even when the runner reported the capture left for the handler.

Evidence: on the unmodified suite, 128 full runs at 6-8x concurrency produced
6 failing runs, all in the crashed-leader section. On the fixed suite, 216
full runs under the same load produced none. Reverting the self-announcing
section's retire-before-reconcile line reproduces "already owned: self-src"
on the first iteration, confirming the shared mechanism.
)

* fix(bin): keep pending-reply expectations honest on both send legs

Two related asymmetries let the parent-owned secondmate reply guard drop or
nag requests it should not have.

Local delivered-unconfirmed dropped the expectation. A marked request whose
submit read-back stayed unconfirmed (verdict=pending) is the same
not-a-failure outcome the remote leg reports as delivered, but fm-send
discarded the parent's pending-reply record for it, so a request that very
likely landed stopped being tracked entirely. The record now stays armed on
its unconfirmed-delivery marker: a correlated report still resolves it, and
an unanswered one still surfaces through the library's own reconciliation.
Exit 3 and the local rule that an unconfirmed answer never closes a decision
key are unchanged.

Remote replies were nagged for a repost they did not need. A remote mate's
report reaches the parent's status log only through the asynchronous mirror
in fm-procevent-remote-reply.sh, yet the guard read an absent correlated
line as proof the mate never reported - even while the answer was still in
flight, which is the common case because the mirror's poll window is
comparable to the recovery grace. The mirror now publishes one caught-up
watermark from a quiet window, and the guard admits a missing report as
evidence only once that watermark passes the turn that should have produced
it. A genuinely missed report still gets exactly one repost, and a channel
that is behind, unarmed, or broken leaves the request durably open and
un-nagged rather than nagging blind; the mirror escalates its own continuity
failures as before.

Tests: a local unconfirmed secondmate send keeps its expectation armed and
resolvable; a mirrored correlated remote reply resolves with no repost; a
stale or absent watermark withholds the repost while a fresh one still
releases it; a quiet remote window publishes the watermark and retirement
clears it.

* no-mistakes(review): Distinguish preempted polls from quiet windows

* no-mistakes(document): Clarify remote reply channel freshness

* no-mistakes(lint): Annotate shared remote preemption exit constant
…d#2619)

* fix(watch): honor a declared pause on a busy pane's completed-turn bound

A worker that declares an external wait (`paused:`) and then blocks in one
long foreground call - a review-hosting scout parked in a single blocking
`lavish-axi poll`, a bounded watch loop, a rate-limit sleep - keeps its pane
BUSY, so the stale path that already honors declared pauses never ran for it.
The busy-pane completed-turn bound instead routed it straight into
wedge_timer_check, which re-escalated "possible wedge, escalation N" (and, past
the threshold, demand-deep-inspection) every FM_STALE_ESCALATE_SECS for as long
as the review stayed open.

busy_turn_bound_check now owns which absorber takes a crossed bound: a crew
whose own last status line declares an external wait or a verified captain-held
transfer takes the bounded FM_PAUSE_RESURFACE_SECS recheck, and everything else
keeps the unchanged wedge timer. The discriminator is the declaration together
with liveness (the caller has already confirmed the pane is busy), never a
blanket silencing - a crew that declared nothing, or whose pane is not live,
escalates exactly as before, and a declared pause still re-surfaces once per
long cadence so a forgotten wait cannot rot invisibly. Away mode is untouched:
the daemon owns pause triage there and already reads the same vocabulary.

The two call sites also no longer clear pause bookkeeping in the same poll the
pause cadence recorded it, which would have erased the re-surface throttle and
turned the long cadence back into a per-poll re-surface.

Tests: a three-phase regression fixture pins the absorbed pause, its long-cadence
recheck, and the restored wedge escalation once the declaration is lifted on the
same busy over-age pane.

Also de-flakes tests/fm-watch-triage.test.sh, which failed spuriously on a loaded
machine: fixed liveness budgets were reaping watchers mid-startup, so assertions
on post-poll state passed vacuously or failed spuriously. Waits that describe a
poll's outcome now wait for a completed poll cycle via the liveness beacon, the
heartbeat test waits for the heartbeat it asserts on, and every wait_for_exit
budget is the uniform 10s already used elsewhere in the file.

* no-mistakes(review): Fail poll-cycle waits on timeout

* no-mistakes(review): Prevent poll timeout test hangs

* no-mistakes(document): Clarify paused busy-pane supervision
Added guidelines for decision communication to the captain.
Clarify communication protocols with crewmates regarding task delegation and reporting.
* fix(herdr): confirm local steers that native agent-state misses

Herdr can leave agent_status idle for a landed Claude turn and can keep
queued Enter text visible while busy, so fm-send was reporting false
swallows. Confirm those cases through the shared queued-Enter verdict
and a cleared composer, and keep a genuine idle pending composer as
unconfirmed.

* no-mistakes(review): Stop Herdr Enter retries on unreadable composers

* no-mistakes(review): Reject queued delivery when all Herdr Enter sends fail

* no-mistakes(review): Prevent confirmation after failed Herdr Enter

* no-mistakes(review): Pace Herdr retries and clarify submit fallback

* no-mistakes(review): Align Herdr submit docs with idle fallback

* no-mistakes(document): Correct Herdr submit-confirmation documentation
* feat(bin): accept any-origin decision bindings with full-identity keys

An aggregation surface (the bearings board) carries captain answers for holds
across origins, but a binding was one-origin-per-source and the Lavish adapter
capped question keys at 64 chars while real full hold identities measure 69-81.

- fm-decision-hold.sh: bind <source-id> --any-origin records the (any) marker;
  binding prints it verbatim and answers accepts it, so the runner's feed seam
  carries an any-origin source with no runner change. In any-origin mode each
  key is a full hold identity <origin>-decision-<key>, split at its first
  -decision-; a key with no separator (merge/dispatch instructions) is skipped
  and feeds nothing, keeping non-decision answers out of the hold ledger by
  construction. Every existing close guard applies unchanged.
- fm-procevent-lavish.sh: raise the question-key cap 64 -> 128 so a full hold
  identity fits; the slug-shape security property is unchanged.
- tests: cross-origin closure through the real runner seam, an 81-char
  identity through the adapter, cap and shape refusals, routed-work skips,
  nonexistent-identity skips, and idempotent replay.

* feat(bearings): add the /bearings lavish interactive fleet board

/bearings lavish renders the bearings snapshot onto a shipped, reusable board
template and arms it as a Lavish process-event source, so the captain answers
Captain's Call items on the board and firstmate is woken by an ordinary check
wake - no conversational turn ever blocks on a poll.

- .agents/skills/bearings/assets/board-template.html: the shipped template
  (myfirstmate design system inlined, one fm-bearings-board.v1 JSON slot,
  fail-closed schema guard that renders an error card instead of an empty
  fleet). Per-invocation agent work is composing the payload only.
- bin/fm-bearings-board.sh: build/refresh owner - fail-closed payload
  validation, slot injection with a round-trip check and \u003c escaping,
  stable board path, any-origin bind ALWAYS before arm, arm-if-absent.
- bearings SKILL.md: the lavish invocation option, board composition rules,
  board-wake handling, and the captain-ruled merge-click authorization with
  its mandatory safeguards (PR resolved from the task's own meta record,
  wake-time green re-verification, never a red or changed PR, merges only
  through bin/fm-pr-merge.sh, chat echo with the full PR URL).
- process-event-sources SKILL.md: one-line board-wake routing trigger.
- tests: payload refusals, injection round-trip, bind-before-arm, idempotent
  re-arm, and template slot integrity.

Fleet pickup: homes receive this after merge plus a firstmate self-update;
landing timing is coordinated with the main firstmate.

* no-mistakes(review): Harden bearings board validation and wake handling

* no-mistakes(review): Require HTTPS for bearings board PR links

* no-mistakes(review): Fail closed and bound bearings board answers

* no-mistakes(review): Enforce UTF-8 byte limits for board answers

* no-mistakes(review): Serve bearings board before arming and reject empty actions

* no-mistakes(review): Prove bind-before-arm ordering through live answer consumption

* no-mistakes(document): Document bearings board and cross-origin answers
@withally
withally merged commit efd7ee2 into main Aug 21, 2026
11 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants